package cn.bytengine.stem.cluster.netty;

import cn.bytengine.stem.contexts.Context;
import cn.hutool.core.util.StrUtil;
import io.netty.bootstrap.Bootstrap;
import io.netty.buffer.PooledByteBufAllocator;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.socket.DatagramChannel;
import io.netty.channel.socket.InternetProtocolFamily;
import io.netty.channel.socket.nio.NioDatagramChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;

import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.NetworkInterface;

public class ClusterHostNettyMulticastUdpServer extends NettyServer<Bootstrap, Channel> {

    public ClusterHostNettyMulticastUdpServer(Context config) {
        super(config);
    }

    @Override
    protected Bootstrap bootstrap(Context context, int port, ChannelInitializer<? extends Channel> channelInitializer) {
        return new Bootstrap()
                .channelFactory(() -> new NioDatagramChannel(InternetProtocolFamily.IPv4))
                .channel(NioServerSocketChannel.class)
                .localAddress(context.getByType("netty.udp.server.local_address", InetAddress.class), port)
                .option(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT)
                .option(ChannelOption.SO_BACKLOG, context.getOrDefaultInteger("netty.udp.server.backlog", 1024))
                .option(ChannelOption.AUTO_CLOSE, context.getOrDefaultBoolean("netty.udp.server.auto_close", true))
                .option(ChannelOption.SO_REUSEADDR, context.getOrDefaultBoolean("netty.udp.server.reuseaddr", true))
                .option(ChannelOption.IP_MULTICAST_IF, context.getByType("netty.udp.server.multicast.if", NetworkInterface.class))
                .handler(channelInitializer);
    }

    @Override
    public void startup(Context context) throws Exception {
        Integer port = context.getInteger("netty.udp.server.port");
        if (port == null || port < 1) {
            throw new IllegalArgumentException("The 'netty.udp.server.port' value is null or less then the 1.");
        }
        NetworkInterface ni = context.getByType("netty.udp.server.multicast.if", NetworkInterface.class);
        if (ni == null) {
            throw new IllegalArgumentException("The 'netty.udp.server.multicast.if' is not contain in Context.");
        }
        if (context.has("netty.udp.server.local_address")) {
            throw new IllegalArgumentException("The 'netty.udp.server.local_address' is not contain in Context.");
        }
        String multicastGroup = context.getString("netty.udp.server.multicast.group");
        if (StrUtil.isBlank(multicastGroup)) {
            throw new IllegalArgumentException("The 'netty.udp.server.multicast.group' is blank.");
        }
        DatagramChannelInitializerSupplier supplier = context.getByType("netty.udp.server.pipeline_factory", DatagramChannelInitializerSupplier.class);
        if (supplier == null) {
            throw new IllegalArgumentException("ClusterHostNettyMulticastUdpServer startup context not contain the called 'netty.udp.server.pipeline_factory' DatagramChannelInitializerSupplier instance.");
        }
        ChannelInitializer<DatagramChannel> channelInitializer = supplier.get();
        if (channelInitializer == null) {
            throw new IllegalArgumentException("The called 'netty.udp.server.pipeline_factory' DatagramChannelInitializerSupplier result is null in ClusterHostNettyMulticastUdpServer startup context.");
        }

        Bootstrap bootstrap = bootstrap(context, port, channelInitializer);

        try {
            ChannelFuture channelFuture = bootstrap.bind(port).sync();
            DatagramChannel datagramChannel = (DatagramChannel) channelFuture.channel();
            datagramChannel.joinGroup(new InetSocketAddress(multicastGroup, port), ni).sync();
            getChannelGroup().add(datagramChannel);
        } catch (Exception e) {
            shutdown();
            throw e;
        }
    }
}
